from datetime import datetime

from django.db.models import QuerySet

import orjson

from ...models import EventDefinition, SchemaPropertyGroupProperty
from .base import EventDefinitionGenerator


class TypeScriptGenerator(EventDefinitionGenerator):
    def generator_version(self) -> str:
        return "1.0.0"

    def language_name(self) -> str:
        return "TypeScript"

    def generate(
        self, event_definitions: QuerySet[EventDefinition], schema_map: dict[str, list[SchemaPropertyGroupProperty]]
    ) -> str:
        """Generate complete TypeScript module with type definitions and exports"""
        # Generate file header
        header = f"""/**
 * GENERATED FILE - DO NOT EDIT
 *
 * This file was auto-generated by PostHog
 * Generated at: {datetime.now().isoformat()}
 * Generator version: {self.generator_version()}
 *
 * Provides capture() for type-safe events and captureRaw() for flexibility
 */
import originalPostHog from 'posthog-js'
import type {{ CaptureOptions, CaptureResult, PostHog as OriginalPostHog, Properties }} from 'posthog-js'
"""

        # Generate event schemas interface
        event_schemas_lines = [
            "// Define event schemas with their required and optional fields",
            "interface EventSchemas {",
        ]

        for event_def in event_definitions:
            properties = schema_map.get(str(event_def.id), [])
            event_name_json = orjson.dumps(event_def.name).decode("utf-8")

            if not properties:
                event_schemas_lines.append(f"    {event_name_json}: Record<string, any>")
            else:
                event_schemas_lines.append(f"    {event_name_json}: {{")
                for prop in properties:
                    ts_type = self._map_property_type(prop.property_type)
                    optional_marker = "" if prop.is_required else "?"
                    # Use orjson.dumps() for proper escaping of property names
                    prop_name_json = orjson.dumps(prop.name).decode("utf-8")
                    event_schemas_lines.append(f"        {prop_name_json}{optional_marker}: {ts_type}")
                event_schemas_lines.append("    }")

        event_schemas_lines.append("}")
        event_schemas = "\n".join(event_schemas_lines)

        # Generate type aliases
        type_aliases = """
// Type alias for all valid event names
export type EventName = keyof EventSchemas

// Type helper to get properties for a specific event
// Intersects the schema with Record<string, any> to allow additional properties
export type EventProperties<K extends EventName> = EventSchemas[K] & Record<string, any>

// Helper type to check if a type has required properties
type HasRequiredProperties<K extends EventName> = {} extends EventSchemas[K] ? false : true

// Helper to detect if T is exactly 'string' (not a literal)
type IsExactlyString<T> = string extends T ? (T extends string ? true : false) : false
"""

        # Generate TypedPostHog interface
        typed_posthog_interface = """
// Enhanced PostHog interface with typed capture
interface TypedPostHog extends Omit<OriginalPostHog, 'capture'> {
    /**
     * Type-safe capture for defined events, or flexible capture for undefined events
     *
     * Note: For defined events, wrap properties in a variable to allow additional properties:
     * const props = { file_size_b: 100, extra: 'data' }
     * posthog.capture('downloaded_file', props)
     *
     * @example
     * // Defined event with type safety
     * posthog.capture('uploaded_file', {
     *   file_name: 'test.txt',
     *   file_size_b: 100
     * })
     *
     * @example
     * // For events with all optional properties, properties argument is optional
     * posthog.capture('logged_out') // no properties needed
     *
     * @example
     * // Undefined events work with arbitrary properties
     * posthog.capture('custom_event', { whatever: 'data' })
     * posthog.capture('another_event') // or no properties
     */
    // Overload 1: For known events (specific EventName literals)
    // This should match first for all known event names
    capture<K extends EventName>(
        event_name: K,
        ...args: HasRequiredProperties<K> extends true
            ? [properties: EventProperties<K>, options?: CaptureOptions]
            : [properties?: EventProperties<K>, options?: CaptureOptions]
    ): CaptureResult | undefined

    // Overload 2: For undefined events and blocking string variables
    // Only matches if event_name is NOT a known EventName
    // The conditional type rejects broad string type
    capture<T extends string>(
        event_name: IsExactlyString<T> extends true ? never : (T extends EventName ? never : T),
        properties?: Properties | null,
        options?: CaptureOptions
    ): CaptureResult | undefined

    /**
     * Raw capture for any event (original behavior, no type checking)
     *
     * Use capture() for type-safe defined events or flexible undefined events.
     * Use captureRaw() only when you need to bypass all type checking.
     *
     * @example
     * posthog.captureRaw('Any Event Name', { whatever: 'data' })
     */
    captureRaw(event_name: string, properties?: Properties | null, options?: CaptureOptions): CaptureResult | undefined
}
"""

        # Generate implementation
        implementation = """
// Create the implementation
const createTypedPostHog = (original: OriginalPostHog): TypedPostHog => {
    // Create the enhanced PostHog object
    const enhanced: TypedPostHog = Object.create(original)

    // Add capture method (type-safe for defined events, flexible for undefined)
    enhanced.capture = function (event_name: string, ...args: any[]): CaptureResult | undefined {
        const [properties, options] = args
        return original.capture(event_name, properties, options)
    }

    // Add captureRaw method for untyped/flexible event tracking
    enhanced.captureRaw = function (
        event_name: string,
        properties?: Properties | null,
        options?: CaptureOptions
    ): CaptureResult | undefined {
        return original.capture(event_name, properties, options)
    }

    // Proxy to delegate all other properties/methods to the original
    return new Proxy(enhanced, {
        get(target, prop) {
            if (prop in target) {
                return (target as any)[prop]
            }
            return (original as any)[prop]
        },
        set(target, prop, value) {
            ;(original as any)[prop] = value
            return true
        },
    })
}
"""

        # Generate exports
        exports = """
// Create and export the typed instance
const posthog = createTypedPostHog(originalPostHog as OriginalPostHog)

export default posthog
export type { EventSchemas, TypedPostHog }

// Re-export everything else from posthog-js
export * from 'posthog-js'

/**
 * USAGE GUIDE
 * ===========
 *
 * For type-safe defined events (recommended):
 *   posthog.capture('uploaded_file', { file_name: 'test.txt', file_size_b: 100 })
 *
 * For undefined events (flexible):
 *   posthog.capture('Custom Event', { whatever: 'data' })
 *
 * For bypassing all type checking (rare):
 *   posthog.captureRaw('Any Event', { whatever: 'data' })
 */
"""

        # Combine all sections
        return header + event_schemas + type_aliases + typed_posthog_interface + implementation + exports

    def _map_property_type(self, property_type: str) -> str:
        type_map = {
            "String": "string",
            "Numeric": "number",
            "Boolean": "boolean",
            "DateTime": "string | Date",
            "Array": "any[]",
            "Object": "Record<string, any>",
        }
        return type_map.get(property_type, "any")
